package com.example.sharding.sharding;


import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import org.springframework.util.Assert;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
public class DateStdShardingAlgorithm implements StandardShardingAlgorithm<Date> {

    private Date lowerDate;

    private Date upperDate;

    private Integer interval;

    private Properties properties;

    private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
    private static final String DATE_PATTERN_MONTH = "yyyyMM";

    @Override
    public void init(Properties properties) {
        String lowerLimit = (String) properties.get("lowerLimit");
        String upperLimit = (String) properties.get("upperLimit");
        String interval = (String) properties.get("interval");
        Assert.notNull(lowerLimit, "lowerLimit can't be empty");
        Assert.notNull(upperLimit, "upperLimit can't be empty");
        Assert.notNull(interval, "interval can't be empty");
        this.lowerDate = parseDate(lowerLimit);
        this.upperDate = parseDate(upperLimit);
        this.interval = Integer.parseInt(interval);
        this.properties = properties;
    }

    private Date parseDate(String lowerLimit) {
        Date date = null;
        SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN);
        try {
            date = sdf.parse(lowerLimit);
        } catch (ParseException e) {
            log.error("String date parse error:", e);
            //throw new CommonException(e.getMessage());
        }
        return date;
    }

    private String formatDate(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN);
        return sdf.format(date);
    }

    private String getYearMonth(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN_MONTH);
        return sdf.format(date);
    }

    private int getYear(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.YEAR);
    }

    private int getMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.MONTH) + 1;
    }

    /**
     * Get properties.
     *
     * @return properties
     */
    @Override
    public Properties getProps() {
        return properties;
    }

    /**
     * Get type.
     *
     * @return type
     */
    @Override
    public String getType() {
        return "QUARTER_DATE";
    }

    /**
     * Sharding.
     *
     * @param tableNames    available data sources or table names
     * @param shardingValue sharding value
     * @return sharding result for data source or table name
     */
    @Override
    public String doSharding(Collection<String> tableNames, PreciseShardingValue<Date> shardingValue) {
        //log.info("tableNames={},shardingValue={},properties={}", JSON.toJSONString(tableNames), JSON.toJSONString(shardingValue), JSON.toJSONString(properties));
        Date date = shardingValue.getValue();
        Assert.isTrue(!(date.getTime() < lowerDate.getTime()), formatDate(date) + " is before lowerLimit: " + formatDate(lowerDate));
        Assert.isTrue(!(date.getTime() > upperDate.getTime()), formatDate(date) + " is after upperLimit: " + formatDate(upperDate));
        String dateStr = getYearMonth(date);
        int idx = calTableIdx(date);
        log.info("idx={}", idx);
        String targetTable = "";
        for (String tableName : tableNames) {
            String tblIdxStr = tableName.substring(tableName.lastIndexOf("_") + 1);
//            int tblIdx = Integer.parseInt(tblIdxStr);
//            if (tblIdx == idx) {
//                targetTable = tableName;
//                break;
//            }
            if (tblIdxStr.equals(dateStr)) {
                targetTable = tableName;
                break;
            }
        }
        log.info("targetTable={}", targetTable);
        return targetTable;
    }

    private int calTableIdx(Date date) {
        int months = (getYear(date) - getYear(lowerDate)) * 12 + getMonth(date);
        int flag = months % interval == 0 ? 0 : 1;
        return months / interval + flag;
    }

    /**
     * Sharding.
     *
     * @param tableNames    available data sources or table names
     * @param shardingValue sharding value
     * @return sharding results for data sources or table names
     */
    @Override
    public Collection<String> doSharding(Collection<String> tableNames, RangeShardingValue<Date> shardingValue) {
        //log.info("tableNames={},shardingValue={},properties={}", JSON.toJSONString(tableNames), JSON.toJSONString(shardingValue), JSON.toJSONString(properties));

        Date lowDate = shardingValue.getValueRange().lowerEndpoint();
        Date upDate = shardingValue.getValueRange().upperEndpoint();
        Assert.isTrue(!(lowDate.getTime() > upDate.getTime()), formatDate(lowDate) + " is after upperEndpoint: " + formatDate(upDate));
        Assert.isTrue(!(lowDate.getTime() < lowerDate.getTime()), formatDate(lowDate) + " is before lowerLimit: " + formatDate(lowerDate));
        Assert.isTrue(!(upDate.getTime() > upperDate.getTime()), formatDate(upDate) + " is after upperLimit: " + formatDate(upperDate));
        //int lowIdx = calTableIdx(lowDate);
        //int upIdx =  calTableIdx(upDate);

        int lowIndex = Integer.parseInt(getYearMonth(lowDate));
        int upIndex = Integer.parseInt(getYearMonth(upDate));

        String lowDateStr = getYearMonth(lowDate);
        String upDateStr = getYearMonth(upDate);
        int lowIdx = Integer.parseInt(lowDateStr);
        int upIdx = Integer.parseInt(upDateStr);

        log.info("lowIdx={},upIdx={}", lowIdx, upIdx);
        List<String> targetTbls = new ArrayList<>();
        for (String tableName : tableNames) {
            String dsIdxStr = tableName.substring(tableName.lastIndexOf("_") + 1);
            int dsIdx = Integer.parseInt(dsIdxStr);
            if (dsIdx >= lowIndex && dsIdx <= upIndex) {
                targetTbls.add(tableName);
            }
        }
        log.info("target table names={}", targetTbls);
        return targetTbls;
    }
}

